<?php
class NoBusinessBase_Tcp_Client
{
   private $mSocket = null;
   public $mChannelsRequested = false;
   //private $onMessageCallback = null;
   //private $mChannelMap = null;
   //private $mChannelsReceived;
   const HEADER_LENGTH = 4;
   const CONTROL_LENGTH = 4;

   public function __construct($host, $port, $tls = false)
   {
      if($tls)
         $host = 'tls://' . $host;

      $this->_connect($host, $port);

     // $this->mChannelsReceived = false;
   }

   public function __destruct()
   {
   }

   public function close()
   {
      $this->_disconnect();
   }

   public function connected()
   {
      return ($this->mSocket != null);
   }

   /*
   public function fillChannelMap($msgJson)
   {
      if(!array_key_exists('channels', $msgJson))
         return;

      $this->mChannelMap = array();
      $map = $msgJson['channels'];
      foreach($map as $channel)
         $this->mChannelMap[key($channel)] = $channel[key($channel)];

      $this->mChannelsReceived = true;
   }

   public function invokeOnMessageCallback($id, $message)
   {
      if(!$this->onMessageCallback)
         return;

      call_user_func($this->onMessageCallback, $id, $message);
   }
   */

   public function readData()
   {
      if(!$this->mSocket)
         return false;

      $headerData = fread($this->mSocket, self::HEADER_LENGTH + self::CONTROL_LENGTH);
      $decodedHeaderControl = $this->_decodeHeader($headerData);
      $bodyLength = $decodedHeaderControl["bodyLength"];
      $channel = $decodedHeaderControl["channel"];
      $bodyData = fread($this->mSocket, $bodyLength);

      return array(
         "channel" => $channel,
         "message" => $bodyData
      );
   }

   public function sendData($channel, $message)
   {
      if(!$this->mSocket)
         return false;

   /*
      //Figure out if the type of the channel is a string or int.
      $type = gettype($outchannel);

      //Send integer channels immediately.
      if($type == "integer")
      {
         $this->_encodeAndSend($outchannel, $message);
         return true;
      }

      //Return early here if the channel is not a string.
      if($type != "string")
         return false;

      //If we haven't received the channel list, return (it's a blocking read, so if it
      //isn't here yet it isn't coming).
      if(!$this->mChannelsReceived)
         return false;

      //If the channel name isn't set in the map, return.
      if(!array_key_exists($outchannel, $this->mChannelMap))
         return false;

      */
      $this->_encodeAndSend($channel, $message); 
      return true;
   }

   /*
   public function setOnMessageCallback($callback)
   {
      $this->onMessageCallback = $callback;
   }

   public function subscribeTo($channels)
   {
      $channelList = array();
      foreach($channels as $channel)
      {
         $type = gettype($channel); 
         if($type == "integer")
         {
            array_push($channelList, $channel);
            continue;
         }
         if($type == "string")
         {
            if(!$this->mChannelsReceived)
               continue;

            if(!array_key_exists($channel, $this->mChannelMap))
               continue;

            array_push($channelList, $this->mChannelMap[$channel]);
            continue;
         }
      }

      $msgJson = array("channels" => $channelList);
      $this->sendData(3, Zend_Json::encode($msgJson));
   }
   */

   private function _connect($host, $port)
   {
      $this->mSocket = stream_socket_client("{$host}:{$port}");
      return $this->mSocket !== false;
   }

   private function _decodeHeader($headerData)
   {
      return unpack("VbodyLength/Vchannel", $headerData);
   }

   private function _disconnect()
   {
      if(!$this->mSocket)
         return;

      fclose($this->mSocket);
      $this->mSocket = null;
   }

   private function _encodeAndSend($outchannel, $message)
   {
      $msg = $this->_encodeHeader($outchannel, json_encode($message));

      $length = strlen($msg); 
      while(true) 
      { 
         $sent = fwrite($this->mSocket,$msg,$length); 
         if($sent === false) 
         { 
             return false; 
         } 
         if($sent < $length) 
         { 
             $msg = substr($msg, $sent); 
             $length -= $sent; 
         } 
         else 
         { 
             return true; 
         } 
      } 
      return false;
   }

   private function _encodeHeader($channel, $message)
   {
      $bodyLength = strlen($message);
      $packed = pack("VV", $bodyLength, $channel);
      $encoded = $packed . $message;
      return $encoded;
   }
}

